import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Tuple, Dict
from .base_trainer import BaseTrainer

class ImageClassifier(nn.Module):
    def __init__(self, num_classes: int):
        super(ImageClassifier, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, 3)
        self.conv2 = nn.Conv2d(32, 64, 3)
        self.conv3 = nn.Conv2d(64, 64, 3)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(64 * 4 * 4, 64)
        self.fc2 = nn.Linear(64, num_classes)
        self.dropout = nn.Dropout(0.5)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(-1, 64 * 4 * 4)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

class ImageClassifierTrainer(BaseTrainer):
    def __init__(self, model: ImageClassifier, device: str, params: Dict):
        super().__init__(model, device, params)
        self.criterion = nn.CrossEntropyLoss()
    
    def train_step(self, x: torch.Tensor, y: torch.Tensor) -> Tuple[float, float]:
        self.model.train()
        x = x.to(self.device)
        y = y.to(self.device)
        
        self.optimizer.zero_grad()
        outputs = self.model(x)
        loss = self.criterion(outputs, y)
        
        loss.backward()
        self.optimizer.step()
        
        # 计算准确率
        _, predicted = torch.max(outputs.data, 1)
        accuracy = (predicted == y).sum().item() / y.size(0)
        
        return loss.item(), accuracy
    
    def evaluate(self, x: torch.Tensor, y: torch.Tensor) -> Tuple[float, float]:
        self.model.eval()
        with torch.no_grad():
            x = x.to(self.device)
            y = y.to(self.device)
            outputs = self.model(x)
            loss = self.criterion(outputs, y)
            
            _, predicted = torch.max(outputs.data, 1)
            accuracy = (predicted == y).sum().item() / y.size(0)
            
        return loss.item(), accuracy